$LOAD_PATH << "../../lib"

require "starruby"
require "starframe"

class MainScene < StarFrame::Scene
  def init *args
    @board = Board.new @screen, 4, 4
    @board.init
  end
  
  def update
    exit_scene if StarRuby::Input.keys(:keyboard).include? :escape
    
    if @board.playing
      input_move_keys = StarRuby::Input.keys(:keyboard, :duration => 1)
      
      move_keys = input_move_keys & [:right, :left, :up, :down]
      moved = 
        unless move_keys.empty?
          moved = @board.move move_keys.first
        else
          if StarRuby::Input.keys(:mouse, :duration => 1).include? :left
            @board.move_click *StarRuby::Input.mouse_location
          end
        end
      
      if moved
        @board.animation(:clear).start if @board.completed?
      end
    end
    @board.call
  end
  
  def render; @board.render; end
  
  
  class Board
    include StarFrame::Eventable
    include StarFrame::Sprite::Animatable
    
    attr_reader :playing
    
    animation :initialize, :start => false, :loop => false do
      frame do
        @playing = false
        self[@x, @y].animation(:hide).start
      end
      wait 45
      frame do
        500.times do
          move [:left, :right, :up, :down][rand 4]
        end
        @width.times{  move :left }
        @height.times{ move :up   }
        @playing = true
      end
    end
    animation :clear, :start => false, :loop => false do
      frame 30 do
        @playing = false
        self[@x, @y].animation(:show).start
      end
      wait 30
      frame do
        call_init
      end
    end
    
    def initialize screen, width, height
      @screen = screen
      @width, @height = width, height
      
      @src_width  = screen.width  / width
      @src_height = screen.height / height
      
      @parts = StarFrame::Sprite::Collection.new
      @field = Array.new(height){ Array.new(width){} }
      height.times do |y|
        width.times do |x|
          @parts << self[x, y] = PartSprite.new(0, 0, :px => x, :py => y, :width => @src_width, :height => @src_height)
        end
      end
      
      @images = Dir.glob("images/*.png")
      @image_index = -1
      
      call_init
    end
    
    def init
      @image_index = (@image_index+1)%@images.length
      load @images[@image_index]
    end
    
    def update; @parts.call; end
    def render; @parts.render_to @screen; end
    
    def [] px, py; @field[py][px]; end
    def []= px, py, part
      part.px = px
      part.py = py
      @field[py][px] = part
    end
    
    def move direction
      tx, ty = @x, @y
      
      case direction
      when :left
        tx += 1
      when :right
        tx -= 1
      when :up
        ty += 1
      when :down
        ty -= 1
      else
        return false
      end
      
      return false unless (0...@width).include?(tx) && (0...@height).include?(ty)
      
      swap @x, @y, tx, ty
      @x, @y = tx, ty
      true
    end
    
    def move_click x, y
      cx, cy = x/@src_width, y/@src_height
      
      if cx == @x
        if cy > @y
          (cy-@y).times{ move :up }
        elsif cy < @y
          (@y-cy).times{ move :down }
        else
          return false
        end
      elsif cy == @y
        if cx > @x
          (cx-@x).times{ move :left }
        elsif cx < @x
          (@x-cx).times{ move :right }
        else
          return false
        end
      else
        return false
      end
      return true
    end
    
    def load name
      loaded_image = StarRuby::Texture.load name
      
      scale = [@screen.width.quo(loaded_image.width), @screen.height.quo(loaded_image.height)].max
      
      image = StarRuby::Texture.new @screen.width, @screen.height
      left = ((@screen.width-loaded_image.width*scale)/2)
      top  = ((@screen.height-loaded_image.height*scale)/2)
      image.render_texture loaded_image, left, top, :scale_x => scale, :scale_y => scale
      
      @parts.each{ |part| part.texture = image }
      @x, @y = @width-1, @height-1
      
      animation(:initialize).start
    end
    
    def completed?
      @parts.all?(&:just?)
    end
    
    private
    def swap x1, y1, x2, y2
      swap = self[x1, y1]
      self[x1, y1] = self[x2, y2]
      self[x2, y2] = swap
    end
  end
  
  class PartSprite < StarFrame::Sprite
    include StarFrame::Sprite::Animatable
    
    animation :hide, :start => false, :loop => false do
      frames 30 do |c|
        self.alpha = 0xFF*(29-c)/30
      end
    end
    animation :show, :start => false, :loop => false do
      frames 30 do |c|
        self.alpha = 0xFF*(1+c)/30
      end
    end
    
    def init options
      self.px = @fx = options[:px]
      self.py = @fy = options[:py]
      
      self.src_width  = options[:width]
      self.src_height = options[:height]
    end
    
    def texture= texture
      @texture = texture
      self.x = self.src_x = @px*@src_width
      self.y = self.src_y = @py*@src_height
      self.alpha = 0xFF
    end
    
    def px= px
      @x = px*@src_width
      @px = px
    end
    def py= py
      @y = py*@src_height
      @py = py
    end
    
    def just?
      (@px == @fx) && (@py == @fy)
    end
  end
end

StarFrame.start :width => 300, :height => 300, :title => "Fifteen Sliding Puzzle", :cursor => true
